﻿using System;
using System.Diagnostics;
using OverStore.Runtime.ObjectRepository.CallbackManager;
using OverStore.Runtime.ObjectRepository.Attacher;
using System.Collections.Generic;
using OverStore.Runtime.ObjectRepository.DeferredLoader;
using OverStore.Runtime.ObjectRepository.Depot;
using OverStore.Runtime.ObjectRepository.Mapping;
using OverStore.Runtime.ObjectRepository.CommandSet;
using OverStore.Runtime.ObjectRepository.ChangesTracker;
using OverStore.Runtime.ObjectRepository.ErrorTransformer;
using OverStore.Runtime.ObjectRepository.Reader;
using OverStore.Configuration.ObjectRepository;
using OverStore.Configuration.ObjectRepository.ErrorTransformer;
using OverStore.Runtime.PersistenceServiceProvider;
using OverStore.Configuration.ObjectRepository.CallbackManager;

namespace OverStore.Configuration.Builder.Fluent
{
    /// <summary>
    /// Contains set of nested classes helps to configure object repository.
    /// </summary>
    /// <typeparam name="TInstance">The type of the instance.</typeparam>
    /// <typeparam name="TIdentifier">The type of the identifier.</typeparam>
    public static class ObjectRepositoryBuilder<TInstance, TIdentifier>
        where TInstance : class
    {
        /// <summary>
        /// Parameters are required for creating object repository.
        /// </summary>
        public class BuilderParameters
        {
            private readonly OverStoreConfigurationBuilder.RepositoryStep _RepositoryStep;

            /// <summary>
            /// Initializes a new instance of the <see cref="BuilderParameters"/> class.
            /// </summary>
            /// <param name="repositoryStep">The repository step.</param>
            public BuilderParameters(
                OverStoreConfigurationBuilder.RepositoryStep repositoryStep)
            {
                if (Object.ReferenceEquals(repositoryStep, null))
                {
                    throw new ArgumentNullException("repositoryStep");
                }
                _RepositoryStep = repositoryStep;
                ParameterizedReaders = new List<IComponentConfiguration<IReader<TInstance, TIdentifier>>>();
            }

            /// <summary>
            /// Gets or sets the callback manager.
            /// </summary>
            /// <value>The callback manager.</value>
            public IComponentConfiguration<ICallbackManager<TInstance>> CallbackManager { get; set; }

            /// <summary>
            /// Gets or sets the deferred loader.
            /// </summary>
            /// <value>The deferred loader.</value>
            public IComponentConfiguration<IDeferredLoader<TInstance>> DeferredLoader { get; set; }

            /// <summary>
            /// Gets or sets the depot.
            /// </summary>
            /// <value>The depot.</value>
            public IComponentConfiguration<IDepot<TInstance, TIdentifier>> Depot { get; set; }

            /// <summary>
            /// Gets or sets the mapper.
            /// </summary>
            /// <value>The mapper.</value>
            public IComponentConfiguration<IMapper<TInstance>> Mapper { get; set; }

            /// <summary>
            /// Gets or sets the commands.
            /// </summary>
            /// <value>The commands.</value>
            public IComponentConfiguration<ICommandSet> Commands { get; set; }

            /// <summary>
            /// Gets or sets the changes tracker.
            /// </summary>
            /// <value>The changes tracker.</value>
            public IComponentConfiguration<IChangesTracker<TInstance>> ChangesTracker { get; set; }

            /// <summary>
            /// Gets or sets the error transformer.
            /// </summary>
            /// <value>The error transformer.</value>
            public IComponentConfiguration<IErrorTransformer<TInstance>> ErrorTransformer { get; set; }

            /// <summary>
            /// Gets or sets the attacher.
            /// </summary>
            /// <value>The attacher.</value>
            public IComponentConfiguration<IAttacher<TInstance, TIdentifier>> Attacher { get; set; }

            /// <summary>
            /// Gets or sets the parameterless reader.
            /// </summary>
            /// <value>The parameterless reader.</value>
            public IComponentConfiguration<IReader<TInstance, TIdentifier>> ParameterlessReader { get; set; }

            /// <summary>
            /// Gets or sets the parameterized readers.
            /// </summary>
            /// <value>The parameterized readers.</value>
            public List<IComponentConfiguration<IReader<TInstance, TIdentifier>>> ParameterizedReaders { get; private set; }

            /// <summary>
            /// Creates the repository.
            /// </summary>
            public PersistenceObjectRepositoryConfiguration<TInstance, TIdentifier> CreateRepository()
            {
                return new PersistenceObjectRepositoryConfiguration<TInstance, TIdentifier>
                {
                    CallbackManager = CallbackManager,
                    ChangesTracker = ChangesTracker,
                    Commands = Commands,
                    DeferredLoader = DeferredLoader,
                    Depot = Depot,
                    ErrorTransformer = ErrorTransformer,
                    Mapper = Mapper,
                    Attacher = Attacher,
                    ParameterlessReader = ParameterlessReader,
                    ParameterizedReaders = ParameterizedReaders
                };
            }

            /// <summary>
            /// Completes this instance.
            /// </summary>
            public OverStoreConfigurationBuilder.RepositoryStep Complete()
            {
                _RepositoryStep.AddRepository(CreateRepository());
                return _RepositoryStep;
            }
        }

        /// <summary>
        /// Intialize callback manager.
        /// </summary>
        public class CallbackManagerStep
        {
            private readonly BuilderParameters _Params;

            /// <summary>
            /// Initializes a new instance of the <see cref="ObjectRepositoryBuilder&lt;TInstance, TIdentifier&gt;.CallbackManagerStep"/> class.
            /// </summary>
            /// <param name="parameters">The parameters.</param>
            public CallbackManagerStep(BuilderParameters parameters)
            {
                Debug.Assert(parameters != null, "parameters != null");
                _Params = parameters;
            }

            /// <summary>
            /// Sets the specified deferred loader.
            /// </summary>
            public DeferredLoaderStep WithCallback(IComponentConfiguration<ICallbackManager<TInstance>> callbackManager)
            {
                _Params.CallbackManager = callbackManager;
                return new DeferredLoaderStep(_Params);
            }

            /// <summary>
            /// Registers function are called on persistence operation.
            /// </summary>
            public DeferredLoaderStep AddCallback(Func<TInstance, PersistenceOperation, IPersistenceServiceProvider, bool> callbackFunction)
            {
                if (Object.ReferenceEquals(callbackFunction, null))
                    throw new ArgumentNullException("callbackFunction");
                var ccm = _Params.CallbackManager as CompositeCallbackManagerConfiguration<TInstance>;
                if (ccm == null)
                {
                    ccm = new CompositeCallbackManagerConfiguration<TInstance>();
                    if (_Params.CallbackManager != null)
                        ccm.Callbacks.Add(_Params.CallbackManager);
                    _Params.CallbackManager = ccm;
                }
                ccm.Callbacks.Add(new DelegateCallbackManagerConfiguration<TInstance>
                    {
                        CallbackDelegates = new[] { callbackFunction }
                    });
                return new DeferredLoaderStep(_Params);
            }

            /// <summary>
            /// Registers function are called on persistence operation.
            /// </summary>
            public DeferredLoaderStep AddCallback(Func<TInstance, PersistenceOperation, bool> callbackFunction)
            {
                if (Object.ReferenceEquals(callbackFunction, null))
                    throw new ArgumentNullException("callbackFunction");
                return AddCallback((entity, operation, provider) => callbackFunction(entity, operation));
            }
        }

        /// <summary>
        /// Intialize deferred loader.
        /// </summary>
        public class DeferredLoaderStep
        {
            private readonly BuilderParameters _Params;

            /// <summary>
            /// Initializes a new instance of the <see cref="ObjectRepositoryBuilder&lt;TInstance, TIdentifier&gt;.DeferredLoaderStep"/> class.
            /// </summary>
            /// <param name="parameters">The parameters.</param>
            public DeferredLoaderStep(BuilderParameters parameters)
            {
                Debug.Assert(parameters != null, "parameters != null");
                _Params = parameters;
            }

            /// <summary>
            /// Sets up the specified deferred loader.
            /// </summary>
            public DepotStep WithDeferredLoader(IComponentConfiguration<IDeferredLoader<TInstance>> deferredLoader)
            {
                _Params.DeferredLoader = deferredLoader;
                return new DepotStep(_Params);
            }
        }

        /// <summary>
        /// Initialize depot.
        /// </summary>
        public class DepotStep
        {
            private readonly BuilderParameters _Params;

            /// <summary>
            /// Initializes a new instance of the <see cref="ObjectRepositoryBuilder&lt;TInstance, TIdentifier&gt;.DepotStep"/> class.
            /// </summary>
            /// <param name="parameters">The parameters.</param>
            public DepotStep(BuilderParameters parameters)
            {
                Debug.Assert(parameters != null, "parameters != null");
                _Params = parameters;
            }

            /// <summary>
            /// Initializes repository with depot.
            /// </summary>
            public MapperStep WithDepot(IComponentConfiguration<IDepot<TInstance, TIdentifier>> depot)
            {
                if (Object.ReferenceEquals(depot, null))
                    throw new ArgumentNullException("depot");
                _Params.Depot = depot;
                return new MapperStep(_Params);
            }
        }

        /// <summary>
        /// Intialize mapper.
        /// </summary>
        public class MapperStep
        {
            private readonly BuilderParameters _Params;

            /// <summary>
            /// Initializes a new instance of the <see cref="ObjectRepositoryBuilder&lt;TInstance, TIdentifier&gt;.MapperStep"/> class.
            /// </summary>
            /// <param name="parameters">The parameters.</param>
            public MapperStep(BuilderParameters parameters)
            {
                Debug.Assert(parameters != null, "parameters != null");
                _Params = parameters;
            }

            /// <summary>
            /// Sets up repository with specified mapper.
            /// </summary>
            public CommandsStep WithMapper(IComponentConfiguration<IMapper<TInstance>> mapper)
            {
                if (Object.ReferenceEquals(mapper, null))
                    throw new ArgumentNullException("mapper");
                _Params.Mapper = mapper;
                return new CommandsStep(_Params);
            }
        }

        /// <summary>
        /// Intialize commands
        /// </summary>
        public class CommandsStep
        {
            private readonly BuilderParameters _Params;

            /// <summary>
            /// Initializes a new instance of the <see cref="ObjectRepositoryBuilder&lt;TInstance, TIdentifier&gt;.CommandsStep"/> class.
            /// </summary>
            /// <param name="parameters">The parameters.</param>
            public CommandsStep(BuilderParameters parameters)
            {
                Debug.Assert(parameters != null, "parameters != null");
                _Params = parameters;
            }

            /// <summary>
            /// Initializes object repository with specified command.
            /// </summary>
            public ChangesTrackerStep WithCommands(IComponentConfiguration<ICommandSet> commands)
            {
                if (Object.ReferenceEquals(commands, null))
                    throw new ArgumentNullException("commands");
                _Params.Commands = commands;
                return new ChangesTrackerStep(_Params);
            }

            /// <summary>
            /// Begins command set configuration.
            /// </summary>
            public CommandSetBuilder<ChangesTrackerStep>.SelectCommandStep CommandSet()
            {
                return new CommandSetBuilder<ChangesTrackerStep>.SelectCommandStep(
                    new CommandSetBuilder<ChangesTrackerStep>.Parameters(c => WithCommands(c)));
            }
        }

        /// <summary>
        /// Intialize changes tracker.
        /// </summary>
        public class ChangesTrackerStep
        {
            private readonly BuilderParameters _Params;

            /// <summary>
            /// Initializes a new instance of the <see cref="ObjectRepositoryBuilder&lt;TInstance, TIdentifier&gt;.ChangesTrackerStep"/> class.
            /// </summary>
            /// <param name="parameters">The parameters.</param>
            public ChangesTrackerStep(BuilderParameters parameters)
            {
                Debug.Assert(parameters != null, "parameters != null");
                _Params = parameters;
            }

            /// <summary>
            /// Initializes repository with the specified changes tracker.
            /// </summary>
            public ErrorTransformerStep WithChangesTracker(IComponentConfiguration<IChangesTracker<TInstance>> changesTracker)
            {
                _Params.ChangesTracker = changesTracker;
                return new ErrorTransformerStep(_Params);
            }
        }

        /// <summary>
        /// Intialize error transformer.
        /// </summary>
        public class ErrorTransformerStep
        {
            private readonly BuilderParameters _Params;

            /// <summary>
            /// Initializes a new instance of the <see cref="ObjectRepositoryBuilder&lt;TInstance, TIdentifier&gt;.ErrorTransformerStep"/> class.
            /// </summary>
            /// <param name="parameters">The parameters.</param>
            public ErrorTransformerStep(BuilderParameters parameters)
            {
                Debug.Assert(parameters != null, "parameters != null");
                _Params = parameters;
            }

            /// <summary>
            /// Initializes repository with the specified error transformer.
            /// </summary>
            public AttacherStep WithErrorTransforming(IComponentConfiguration<IErrorTransformer<TInstance>> errorTransformer)
            {
                _Params.ErrorTransformer = errorTransformer;
                return new AttacherStep(_Params);
            }

            /// <summary>
            /// Errors the transform.
            /// </summary>
            public ErrorTransformerBuilder<TInstance, AttacherStep>.MatchErrorTransformerStep ErrorTransform()
            {
                var config = new MatchErrorTransformerConfiguration<TInstance>();
                return new ErrorTransformerBuilder<TInstance, AttacherStep>.MatchErrorTransformerStep(
                    config,
                    c => WithErrorTransforming(c));
            }
        }

        /// <summary>
        /// Initializes attacher.
        /// </summary>
        public class AttacherStep
        {
            private readonly BuilderParameters _Params;

            /// <summary>
            /// Initializes a new instance of the <see cref="ObjectRepositoryBuilder&lt;TInstance, TIdentifier&gt;.AttacherStep"/> class.
            /// </summary>
            /// <param name="parameters">The parameters.</param>
            public AttacherStep(BuilderParameters parameters)
            {
                Debug.Assert(parameters != null, "parameters != null");
                _Params = parameters;
            }

            /// <summary>
            /// Initializes repository with the specified attacher.
            /// </summary>
            public EndRepositoryStep WithAttacher(IComponentConfiguration<IAttacher<TInstance, TIdentifier>> attacher)
            {
                _Params.Attacher = attacher;
                return new EndRepositoryStep(_Params);
            }
        }

        /// <summary>
        /// Completes object repository configuration.
        /// </summary>
        public class EndRepositoryStep
        {
            private readonly BuilderParameters _Params;

            /// <summary>
            /// Initializes a new instance of the <see cref="ObjectRepositoryBuilder&lt;TInstance, TIdentifier&gt;.EndRepositoryStep"/> class.
            /// </summary>
            /// <param name="parameters">The parameters.</param>
            public EndRepositoryStep(BuilderParameters parameters)
            {
                if (Object.ReferenceEquals(parameters, null))
                    throw new ArgumentNullException("parameters");
                _Params = parameters;
            }

            /// <summary>
            /// Configures parameterless reader.
            /// </summary>
            public ParameterlessReaderBuilder<TInstance, TIdentifier, EndRepositoryStep>.SetCommandStep ParameterlessReader(bool useResultsetForObjectConstruction)
            {
                var param = new ParameterlessReaderBuilder<TInstance, TIdentifier, EndRepositoryStep>.Params(r =>
                {
                    _Params.ParameterlessReader = r;
                    return this;
                })
                {
                    UseResultsetForObjectConstruction = useResultsetForObjectConstruction
                };
                return new ParameterlessReaderBuilder<TInstance, TIdentifier, EndRepositoryStep>.SetCommandStep(param);
            }

            /// <summary>
            /// Configures parameterized reader.
            /// </summary>
            public ParameterizedReaderBuilder<TInstance, TIdentifier, TParameter, EndRepositoryStep>.SetCommandStep ParameterizedReader<TParameter>(bool useResultsetForObjectConstruction)
                where TParameter : class
            {
                var param = new ParameterizedReaderBuilder<TInstance, TIdentifier, TParameter, EndRepositoryStep>.Param(
                    r =>
                    {
                        _Params.ParameterizedReaders.Add(r);
                        return this;
                    })
                        {
                            UseResultsetForObjectConstruction = useResultsetForObjectConstruction
                        };
                return new ParameterizedReaderBuilder<TInstance, TIdentifier, TParameter, EndRepositoryStep>.SetCommandStep(param);
            }

            /// <summary>
            /// Sets up data comparer.
            /// </summary>
            public OverStoreConfigurationBuilder.RepositoryStep EndObjectRepository()
            {
                return _Params.Complete();
            }
        }
    }
}
